////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) Roman Ranzmaier (http://www.ranzmaier.at/contact.aspx).  All rights reserved.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


using System;
using Microsoft.SPOT;
using System.Text;
using System.IO;
using System.Net.Sockets;

namespace Ranzmaier.NETMF.IO
{

    /// <summary>
    /// Reads primitive data types as binary values. 
    /// This class corresponds to the .net class BinaryReader 
    /// NOTE: .net Micro framework only supports UTF8 Encoding
    /// the type <see cref="System.Decimal">System.Decimal</see> is not supported because my (and maybe all) hardware does not support it.
    /// </summary>
    public class BinaryReader : IDisposable
    {
        /// <summary>
        /// Holds the underlying stream.
        /// </summary>
        protected Stream _stream;        
        private bool isNetworkStream = false;
        byte[] _buffer = new byte[8];

        /// <summary>
        /// Initializes a new instance of the <see cref="BinaryReader"/> class.
        /// </summary>
        /// <param name="stream">The supplied stream</param>
        /// <exception cref="T:System.ArgumentException">The stream does not support reading, the stream is null, or the stream is already closed. </exception>
        public BinaryReader(Stream stream)
        {
            if (stream == null)
            {
                throw new ArgumentNullException("stream");
            }
            if (!stream.CanRead)
            {
                throw new ArgumentException("Stream not readable");
            }
            _stream = stream;
            isNetworkStream = _stream is NetworkStream;

        }
        /// <summary>
        ///  Exposes access to the underlying stream of the <see cref="BinaryReader">BinaryReader</see>
        /// </summary>
        public Stream BaseStream
        {
            get
            {
                return _stream;
            }
        }
        /// <summary>
        /// <summary>Closes the current reader and the underlying stream.</summary>
        /// </summary>
        public virtual void Close()
        {
            this.Dispose();
        }

        protected virtual void ReadToInternalBuffer(int count)
        {
            if (_buffer != null && (count < 0 || count > _buffer.Length))
            {
                throw new ArgumentOutOfRangeException("numBytes", "Out of Range");
            }
            int v = 0;
            int v2;
            if (_stream == null)
                throw new NullReferenceException("Stream not open");
            if (count == 1)
            {
                v2 = _stream.ReadByte();
                if (v2 == -1)
                    throw new IndexOutOfRangeException("End of strem reached");
                _buffer[0] = (byte)v2;
                return;
            }
            do
            {
                v2 = this._stream.Read(_buffer, v, count - v);
                if (v2 == 0)
                    throw new IndexOutOfRangeException("End of strem reached");
                v += v2;
            }
            while (v < count);
        }

        /// <summary>
        /// Reads the specified number of bytes from the stream, starting from a specified point in the byte array.
        /// </summary>
        /// <param name="buffer">The buffer to read data into.</param>
        /// <param name="index">The starting point in the buffer at which to begin reading into the buffer.</param>
        /// <param name="count">The number of bytes to read.</param>
        /// <returns>
        /// The number of bytes read into <paramref name="buffer"/>. This might be less than the number of bytes requested if that many bytes are not available, or it might be zero if the end of the stream is reached.
        /// </returns>
        /// <exception cref="T:System.ArgumentException">The buffer length minus <paramref name="index"/> is less than <paramref name="count"/>. -or-The number of decoded characters to read is greater than <paramref name="count"/>. This can happen if a Unicode decoder returns fallback characters or a surrogate pair.</exception>
        ///   
        /// <exception cref="T:System.ArgumentNullException">
        ///   <paramref name="buffer"/> is null. </exception>
        ///   
        /// <exception cref="T:System.ArgumentOutOfRangeException">
        ///   <paramref name="index"/> or <paramref name="count"/> is negative. </exception>
        ///   
        /// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
        ///   
        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
        public virtual int Read(byte[] buffer, int index, int count)
        {
            if (buffer == null)
                throw new ArgumentNullException("buffer", "Buffer is null");
            if (index < 0)
                throw new ArgumentOutOfRangeException("index", "Index must be greater than 0");
            if (count < 0)
                throw new ArgumentOutOfRangeException("count", "Count must be greater than 0");
            if (buffer.Length - index < count)
                throw new ArgumentException("Invalid lenght");
            if (this._stream == null)
                throw new NullReferenceException("Stream not open");
            return this._stream.Read(buffer, index, count);
        }
        /// <summary>
        /// Reads the specified number of characters from the stream, starting from a specified point in the character array.
        /// </summary>
        /// <param name="buffer">The buffer to read data into.</param>
        /// <param name="index">The starting point in the buffer at which to begin reading into the buffer.</param>
        /// <param name="count">The number of characters to read.</param>
        /// <returns>
        /// The total number of characters read into the buffer. This might be less than the number of characters requested if that many characters are not currently available, or it might be zero if the end of the stream is reached.
        /// </returns>
        /// <exception cref="T:System.ArgumentException">The buffer length minus <paramref name="index"/> is less than <paramref name="count"/>. -or-The number of decoded characters to read is greater than <paramref name="count"/>. This can happen if a Unicode decoder returns fallback characters or a surrogate pair.</exception>
        ///   
        /// <exception cref="T:System.ArgumentNullException">
        ///   <paramref name="buffer"/> is null. </exception>
        ///   
        /// <exception cref="T:System.ArgumentOutOfRangeException">
        ///   <paramref name="index"/> or <paramref name="count"/> is negative. </exception>
        ///   
        /// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
        ///   
        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
        public virtual int Read(char[] buffer, int index, int count)
        {
            if (buffer == null)
                throw new ArgumentNullException("buffer");
            if (index < 0)
                throw new ArgumentOutOfRangeException("index");
            if (count < 0)
                throw new ArgumentOutOfRangeException("count");
            if (buffer.Length - index < count)
                throw new ArgumentException("Invalid lenght");
            if (this._stream == null)
                throw new NullReferenceException("Stream not open");

            byte[] res = new byte[buffer.Length - index];
            
            int bytes = Read(res, 0, count);
            char[] chars = Encoding.UTF8.GetChars(res);
            Array.Copy(chars,0, buffer, index, bytes);
            
            return bytes;

        }
        /// <summary>
        /// Reads the specified number of bytes from the current stream into a byte array and advances the current position by that number of bytes.
        /// </summary>
        /// <param name="count">The number of bytes to read.</param>
        /// <returns>
        /// A byte array containing data read from the underlying stream. This might be less than the number of bytes requested if the end of the stream is reached.
        /// </returns>
        /// <exception cref="T:System.ArgumentException">The number of decoded characters to read is greater than <paramref name="count"/>. This can happen if a Unicode decoder returns fallback characters or a surrogate pair.</exception>
        ///   
        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
        ///   
        /// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
        ///   
        /// <exception cref="T:System.ArgumentOutOfRangeException">
        ///   <paramref name="count"/> is negative. </exception>
        public virtual byte[] ReadBytes(int count)
        {
            byte[] result = new byte[count];
            Read(result, 0, count);
            return result;

        }

        /// <summary>
        /// Reads characters from the underlying stream and advances the current position of the stream in accordance with the Encoding used and the specific character being read from the stream.
        /// </summary>
        /// <returns>
        /// The next character from the input stream, or -1 if no characters are currently available.
        /// </returns>
        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
        ///   
        /// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
        public virtual int Read()
        {
            if (this._stream == null)
                throw new NullReferenceException("Stream not open");
            return this._stream.ReadByte();

        }
        /// <summary>
        /// Reads the next byte from the current stream and advances the current position of the stream by one byte.
        /// </summary>
        /// <returns>
        /// The next byte read from the current stream.
        /// </returns>
        /// <exception cref="T:Ranzmaier.NETMF.IO.EndOfStreamException">The end of the stream is reached. </exception>
        ///   
        /// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
        ///   
        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
        public virtual byte ReadByte()
        {
            int num = Read();
            if (num == -1)
                throw new IndexOutOfRangeException("End of strem reached");
            return (byte)num;
        }
        /// <summary>
        /// Reads a 2-byte signed integer from the current stream and advances the current position of the stream by two bytes.
        /// </summary>
        /// <returns>
        /// A 2-byte signed integer read from the current stream.
        /// </returns>
        /// <exception cref="T:Ranzmaier.NETMF.IO.EndOfStreamException">The end of the stream is reached. </exception>
        ///   
        /// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
        ///   
        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
        public virtual char ReadChar()
        {
            byte[] r = new byte[1];
            r[0] = ReadByte();
            return Encoding.UTF8.GetChars(r)[0];
        }
        /// <summary>
        /// Reads the specified number of characters from the current stream, returns the data in a character array, and advances the current position in accordance with the Encoding used and the specific character being read from the stream.
        /// </summary>
        /// <param name="count">The number of characters to read.</param>
        /// <returns>
        /// A character array containing data read from the underlying stream. This might be less than the number of characters requested if the end of the stream is reached.
        /// </returns>
        /// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
        ///   
        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
        ///   
        /// <exception cref="T:System.ArgumentOutOfRangeException">
        ///   <paramref name="count"/> is negative. </exception>
        public virtual char[] ReadChars(int count)
        {            
            return Encoding.UTF8.GetChars(ReadBytes(count));
        }

        /// <summary>
        /// Reads all bytes from the current stream from the actual position to the end.
        /// </summary>
        /// <returns>
        /// A byte array containing data read from the underlying stream.
        /// </returns>
        /// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
        ///   
        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
        public virtual byte[] ReadToEnd()
        {
            byte[] buf = new byte[1024];
            byte[] result;
            int offset = 0;
            int size = 1024;
            int index = 0;

            using (MemoryStream ms = new MemoryStream())
            {
                while (_stream.CanRead)
                {

                    if (isNetworkStream && !((NetworkStream)_stream).DataAvailable)
                        break; // Socket not available
                    Array.Clear(buf, 0, size);

                    int bytesRead = _stream.Read(buf, offset, size);
                    if (bytesRead == 0)
                        break; // Connection closed

                    ms.Write(buf, 0, bytesRead);
                    index += bytesRead;
                }
                result = ms.ToArray();
            }

            return result;


        }
        /// <summary>
        /// Reads the next character from the current stream and advances the current position of the stream in accordance with the Encoding used and the specific character being read from the stream.
        /// </summary>
        /// <returns>
        /// A character read from the current stream.
        /// </returns>
        /// <exception cref="T:Ranzmaier.NETMF.IO.EndOfStreamException">The end of the stream is reached. </exception>
        ///   
        /// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
        ///   
        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
        ///   
        /// <exception cref="T:System.ArgumentException">A surrogate character was read. </exception>
        public virtual sbyte ReadSByte()
        {
            int num = Read();
            if (num == -1)
                throw new IndexOutOfRangeException("End of strem reached");
            return (sbyte)num;
        }


        /// <summary>
        /// Reads an 8-byte signed integer from the current stream and advances the current position of the stream by eight bytes.
        /// </summary>
        /// <returns>
        /// An 8-byte signed integer read from the current stream.
        /// </returns>
        /// <exception cref="T:Ranzmaier.NETMF.IO.EndOfStreamException">The end of the stream is reached. </exception>
        ///   
        /// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
        ///   
        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
        public virtual long ReadInt64()
        {
            this.ReadToInternalBuffer(8);
            uint num = ((uint)((int)_buffer[0] | (int)_buffer[1] << 8 | (int)_buffer[2] << 16 | (int)_buffer[3] << 24));
            uint num2 = ((uint)((int)_buffer[4] | (int)_buffer[5] << 8 | (int)_buffer[6] << 16 | (int)_buffer[7] << 24));
            return (long)((ulong)num2 << 32 | (ulong)num);

        }
        /// <summary>
        /// Reads a 4-byte signed integer from the current stream and advances the current position of the stream by four bytes.
        /// </summary>
        /// <returns>
        /// A 4-byte signed integer read from the current stream.
        /// </returns>
        /// <exception cref="T:Ranzmaier.NETMF.IO.EndOfStreamException">The end of the stream is reached. </exception>
        ///   
        /// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
        ///   
        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
        public virtual int ReadInt32()
        {
            ReadToInternalBuffer(4);
            return (int)(
              ((_buffer[3] & 0xff) << 24) |
              ((_buffer[2] & 0xff) << 16) |
              ((_buffer[1] & 0xff) << 8) |
              ((_buffer[0] & 0xff)));

        }
        /// <summary>
        /// Reads a 2-byte unsigned integer from the current stream using little-endian encoding and advances the position of the stream by two bytes.
        /// </summary>
        /// <returns>
        /// A 2-byte unsigned integer read from this stream.
        /// </returns>
        /// <exception cref="T:Ranzmaier.NETMF.IO.EndOfStreamException">The end of the stream is reached. </exception>
        ///   
        /// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
        ///   
        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
        public virtual short ReadInt16()
        {
            ReadToInternalBuffer(2);
            return (short)(((_buffer[1] & 0xff) << 8) | (_buffer[0] & 0xff));

        }
        /// <summary>
        /// Reads an 8-byte unsigned integer from the current stream and advances the position of the stream by eight bytes.
        /// </summary>
        /// <returns>
        /// An 8-byte unsigned integer read from this stream.
        /// </returns>
        /// <exception cref="T:Ranzmaier.NETMF.IO.EndOfStreamException">The end of the stream is reached. </exception>
        ///   
        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
        ///   
        /// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
        public virtual ulong ReadUInt64()
        {
            ReadToInternalBuffer(8);
            return (ulong)(
              ((_buffer[7] & 0xff) << 56) |
              ((_buffer[6] & 0xff) << 48) |
              ((_buffer[5] & 0xff) << 40) |
              ((_buffer[4] & 0xff) << 32) |
              ((_buffer[3] & 0xff) << 24) |
              ((_buffer[2] & 0xff) << 16) |
              ((_buffer[1] & 0xff) << 8) |
              ((_buffer[0] & 0xff)));

        }
        /// <summary>
        /// Reads a 4-byte signed integer from the current stream and advances the current position of the stream by four bytes.
        /// </summary>
        /// <returns>
        /// A 4-byte signed integer read from the current stream.
        /// </returns>
        /// <exception cref="T:Ranzmaier.NETMF.IO.EndOfStreamException">The end of the stream is reached. </exception>
        ///   
        /// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
        ///   
        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
        public virtual uint ReadUInt32()
        {
            ReadToInternalBuffer(4);
            return (uint)(
              ((_buffer[3] & 0xff) << 24) |
              ((_buffer[2] & 0xff) << 16) |
              ((_buffer[1] & 0xff) << 8) |
              ((_buffer[0] & 0xff)));

        }
        public virtual ushort ReadUInt16()
        {
            ReadToInternalBuffer(2);
            return (ushort)(((_buffer[0] & 0xff) << 8) | (_buffer[1] & 0xff));

        }

        /// <summary>
        /// Reads an 8-byte floating point value from the current stream and advances the current position of the stream by eight bytes.
        /// </summary>
        /// <returns>
        /// An 8-byte floating point value read from the current stream.
        /// </returns>
        /// <exception cref="T:Ranzmaier.NETMF.IO.EndOfStreamException">The end of the stream is reached. </exception>
        ///   
        /// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
        ///   
        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
        public virtual double ReadDouble()
        {
            byte[] reflectionDouble = new byte[9];
            reflectionDouble[0] = 205; //double
            ReadToInternalBuffer(8);
            for (int i = 0; i < 8; i++)
                reflectionDouble[8 - i] = _buffer[i];
            return (double)Reflection.Deserialize(reflectionDouble, typeof(double));

        }
        /// <summary>
        /// Reads a 4-byte floating point value from the current stream and advances the current position of the stream by four bytes.
        /// </summary>
        /// <returns>
        /// A 4-byte floating point value read from the current stream.
        /// </returns>
        /// <exception cref="T:Ranzmaier.NETMF.IO.EndOfStreamException">The end of the stream is reached. </exception>
        ///   
        /// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
        ///   
        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
        public virtual float ReadSingle()
        {
            byte[] reflectionFloat = new byte[5];
            reflectionFloat[0] = 204; //float
            ReadToInternalBuffer(4);
            for (int i = 0; i < 4; i++)
                reflectionFloat[4 - i] = _buffer[i];
            return (float)Reflection.Deserialize(reflectionFloat, typeof(float));
        }
#if DEBUG
        #region  unsafe float/double experiment
        /*
                public  double ReadDouble()
                {

                    byte[] result = ReadBytes(8);
                    uint num = (uint)((int)result[0] & 0xff | (int)result[1] & 0xff << 8 | (int)result[2] & 0xff << 16 | (int)result[3] & 0xff << 24);
                    uint num2 = (uint)((int)result[4] & 0xff | (int)result[5] & 0xff << 8 | (int)result[6] & 0xff << 16 | (int)result[7] & 0xff << 24);
                    ulong num3 = (ulong)num2 << 32 | (ulong)num;
                    return *(double*)(&num3);
                }

                public  float ReadFloat()
                {
                    byte[] result = ReadBytes(4);
                    uint num = (uint)((int)result[0] | (int)result[1] << 8 );
                    uint num2 = (uint)((int)result[2] | (int)result[3] << 8);
                    ulong num3 = (ulong)num2 << 32 | (ulong)num;
                    return *(float*)(&num3);
                }
                */
        #endregion
#endif
        /// <summary>
        /// Reads a Boolean value from the current stream and advances the current position of the stream by one byte.
        /// </summary>
        /// <returns>
        /// true if the byte is nonzero; otherwise, false.
        /// </returns>
        /// <exception cref="T:Ranzmaier.NETMF.IO.EndOfStreamException">The end of the stream is reached. </exception>
        ///   
        /// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
        ///   
        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
        public virtual bool ReadBoolean()
        {
            int num = Read();
            if (num == -1)
                throw new IndexOutOfRangeException("End of strem reached");

            return num == 1;

        }
        /// <summary>
        /// Reads a string from the current stream. The string is prefixed with the length, encoded as an integer seven bits at a time.
        /// </summary>
        /// <returns>
        /// The string being read.
        /// </returns>
        /// <exception cref="T:Ranzmaier.NETMF.IO.EndOfStreamException">The end of the stream is reached. </exception>
        ///   
        /// <exception cref="T:System.ObjectDisposedException">The stream is closed. </exception>
        ///   
        /// <exception cref="T:System.IO.IOException">An I/O error occurs. </exception>
        public virtual string ReadString()
        {
            //Calculate Lenght
            int lenght = 0;
            int pos = 0;
            byte b = ReadByte();
            while (b > 127)
            {
                lenght += (b - 128) * (int)System.Math.Pow(128, pos++);
                b = ReadByte();
            }
            lenght += b * (int)System.Math.Pow(128, pos);
            //Read String
            return new string(Encoding.UTF8.GetChars(ReadBytes(lenght)));
        }

        /// <summary>
        /// Releases the unmanaged resources used by the <see cref="T:BinaryReader"/> class and optionally releases the managed resources.
        /// </summary>
        public void Dispose()
        {
            if (_stream != null)
                _stream.Close();
            _stream = null;
            _buffer = null;

        }
    }


    public class EndOfStreamException : Exception
    {
        public EndOfStreamException()
            : base("End of stream reached")
        {
        }
        public EndOfStreamException(string message)
            : base(message)
        {
        }
        public EndOfStreamException(string message, Exception inner)
            : base(message, inner)
        {
        }

    }
}
